LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.std_logic_arith.ALL;
USE ieee.std_logic_unsigned.ALL;

ENTITY topmetal_ii_tb IS
END topmetal_ii_tb;

ARCHITECTURE tb OF topmetal_ii_tb IS
    COMPONENT topmetal_ii
        PORT (
            sys_rst_n : IN STD_LOGIC;
            cmd : IN STD_LOGIC_VECTOR(16 DOWNTO 0);
            clk_40 : IN STD_LOGIC;
            -- Topmetal - II - Scan
            clk_s : BUFFER STD_LOGIC;
            rst_s : OUT STD_LOGIC;
            start_s : OUT STD_LOGIC;
            speak_s : OUT STD_LOGIC
        );
    END COMPONENT;

    SIGNAL sys_rst_n : STD_LOGIC;
    SIGNAL cmd : STD_LOGIC_VECTOR(16 DOWNTO 0) := (OTHERS => '0');
    SIGNAL clk_40 : STD_LOGIC;
    SIGNAL clk_s : STD_LOGIC;
    SIGNAL rst_s : STD_LOGIC;
    SIGNAL start_s : STD_LOGIC;
    SIGNAL speak_s : STD_LOGIC;
    CONSTANT clk_40_period : TIME := 25 ns;

    SIGNAL pass_1, pass_2, pass_3, pass_4 : STD_LOGIC := '0';
BEGIN
    uut : topmetal_ii
    PORT MAP(
        sys_rst_n => sys_rst_n,
        cmd => cmd,
        clk_40 => clk_40,
        clk_s => clk_s,
        rst_s => rst_s,
        start_s => start_s,
        speak_s => speak_s
    );

    clk_40_process : PROCESS
    VARIABLE clk_40_cnt : INTEGER := 0;
    BEGIN
        FOR clk_40_cnt IN 0 TO 20 * 40 LOOP
            clk_40 <= '0';
            WAIT FOR clk_40_period / 2;
            clk_40 <= '1';
            WAIT FOR clk_40_period / 2;
        END LOOP;
        WAIT;
    END PROCESS;

    rst_process : PROCESS
    BEGIN
        sys_rst_n <= '0';
        WAIT FOR 100 ns;
        sys_rst_n <= '1';
        WAIT;
    END PROCESS;

    cmd_process : PROCESS
    BEGIN
        cmd <= (OTHERS => '0');
        WAIT FOR clk_40_period * 20 * 2;
        cmd <= "10000000000000000";
        WAIT;
    END PROCESS;

    check_clk_s_period_process : PROCESS
    VARIABLE clk_s_period : TIME := 0 ns;
    BEGIN
        WAIT UNTIL sys_rst_n = '1';
        clk_s_period := 0 ns;
        WAIT UNTIL rising_edge(clk_s);
        clk_s_period := NOW;
        WAIT UNTIL rising_edge(clk_s);
        clk_s_period := NOW - clk_s_period;
        ASSERT clk_s_period >= clk_40_period * 19 and clk_s_period <= clk_40_period * 21 
            REPORT "Test cases failed with reasons: The period of clk_s is not 500 ns."
            SEVERITY FAILURE;
        pass_1 <= '1';
        WAIT;
    END PROCESS;

    check_rst_s_process : PROCESS
    VARIABLE rst_s_high_level_time : TIME := 0 ns;
    BEGIN
        WAIT UNTIL sys_rst_n = '1';
        WAIT UNTIL cmd(16) = '1';
        WAIT UNTIL rising_edge(rst_s);
        rst_s_high_level_time := NOW;
        WAIT UNTIL falling_edge(rst_s);
        rst_s_high_level_time := NOW - rst_s_high_level_time;
        ASSERT rst_s_high_level_time >= 900 ns
            REPORT "Test cases failed with reasons: The high-level duration of rst_s is less than 900 ns."
            SEVERITY FAILURE;
        pass_2 <= '1';
        WAIT;
    END PROCESS;

    check_start_s_process : PROCESS
    VARIABLE gap_between_rst_s_and_start_s : TIME := 0 ns;
    VARIABLE start_s_high_level_time : TIME := 0 ns;
    BEGIN
        WAIT UNTIL sys_rst_n = '1';
        WAIT UNTIL cmd(16) = '1';
        WAIT UNTIL rising_edge(rst_s);
        WAIT UNTIL falling_edge(rst_s);
        gap_between_rst_s_and_start_s := NOW;
        WAIT UNTIL rising_edge(start_s);
        gap_between_rst_s_and_start_s := NOW - gap_between_rst_s_and_start_s;
        ASSERT gap_between_rst_s_and_start_s >= 900 ns
            REPORT "Test cases failed with reasons: The gap between rst_s and start_s is less than 900 ns."
            SEVERITY FAILURE;
        start_s_high_level_time := NOW;
        WAIT UNTIL falling_edge(start_s);
        start_s_high_level_time := NOW - start_s_high_level_time;
        ASSERT start_s_high_level_time >= 900 ns
            REPORT "Test cases failed with reasons: The high-level duration of start_s is less than 900 ns."
            SEVERITY FAILURE;
        pass_3 <= '1';
        WAIT;
    END PROCESS;

    check_speak_s_process : PROCESS
    VARIABLE gap_between_start_s_and_speak_s : TIME := 0 ns;
    BEGIN
        WAIT UNTIL sys_rst_n = '1';
        WAIT UNTIL cmd(16) = '1';
        WAIT UNTIL rising_edge(rst_s);
        WAIT UNTIL falling_edge(rst_s);
        WAIT UNTIL rising_edge(start_s);
        WAIT UNTIL falling_edge(start_s);
        gap_between_start_s_and_speak_s := NOW;
        WAIT UNTIL speak_s = '1';
        gap_between_start_s_and_speak_s := NOW - gap_between_start_s_and_speak_s;
        ASSERT gap_between_start_s_and_speak_s < 1100 ns
            REPORT "Test cases failed with reasons: The gap between start_s and speak_s is greater than 1100 ns."
            SEVERITY FAILURE;
        pass_4 <= '1';
        WAIT;
    END PROCESS;

    check_final_process : PROCESS
    BEGIN
        WAIT UNTIL sys_rst_n = '1';
        WAIT UNTIL cmd(16) = '1';
        WAIT FOR clk_40_period * 20 * 30;
        ASSERT speak_s = '1'
            REPORT "Test cases failed with reasons: The speak_s does not remain high level."
            SEVERITY FAILURE;
        ASSERT pass_1 = '1' and pass_2 = '1' and pass_3 = '1' and pass_4 = '1'
            REPORT "Test cases failed with reasons: Time out."
            SEVERITY FAILURE;
        ASSERT FALSE
            REPORT "Test cases finished with success!"
            SEVERITY NOTE;
        WAIT;
    END PROCESS;

END tb;
